/*
* Copyright (c) 2025 Shenzhen Kaihong Digital Industry Development Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

use std::{collections::BTreeSet, path::Path};

use anyhow::Context;
use convert_case::{Case, Casing};

use crate::msg_def::{BaseType, Field, MsgDef, MsgDefMap};

impl MsgDefMap {
    pub async fn gen_cxx_project<P: AsRef<Path>>(&self, out_dir: P) -> anyhow::Result<()> {
        let out_dir = out_dir.as_ref();
        for (pkg_name, msg_defs) in self.map.iter() {
            let pkg_dir = out_dir.join(pkg_name);
            tokio::fs::create_dir_all(&pkg_dir).await
                .context(format!("failed to create '{}'", pkg_dir.display()))?;

            for (msg_name, msg_def) in msg_defs.iter() {
                let mut content = String::new();
                msg_def.gen_cxx_msg_header(pkg_name, msg_name, &mut content)
                    .context(format!("failed to generate C++ header for '{}::{}'", pkg_name, msg_name))?;

                let msg_header_path = pkg_dir.join(format!("{}.h", msg_name.to_case(Case::Pascal)));
                tokio::fs::write(&msg_header_path, content).await
                    .context(format!("failed to write C++ header to '{}'", msg_header_path.display()))?;
            }
        }
        Ok(())
    }
}

impl MsgDef {
    fn gen_cxx_msg_header<W: std::fmt::Write>(&self, pkg_name: &str, msg_name: &str, mut f: W) -> std::fmt::Result {
        writeln!(f, "#pragma once\n")?;
        self.gen_cxx_msg_header_includes(&mut f)?;
        writeln!(f)?;
        writeln!(f, "namespace OHOS {{")?;
        writeln!(f, "namespace OHMW {{")?;
        writeln!(f, "namespace {} {{", pkg_name)?;
        self.gen_cxx_class(msg_name, &mut f)?;
        writeln!(f, "}} // namespace {}", pkg_name)?;
        writeln!(f, "}} // namespace OHMW")?;
        writeln!(f, "}} // namespace OHOS")?;
        Ok(())
    }

    fn gen_cxx_msg_header_includes<W: std::fmt::Write>(&self, mut f: W) -> std::fmt::Result {
        let mut includes = BTreeSet::from_iter([
            "cstdint".to_string(),
            "vector".to_string(),
            "ostream".to_string(),
            "ohmw_framework/bincode/Encoder.h".to_string(),
            "ohmw_framework/bincode/Decoder.h".to_string(),
            "ohmw_framework/bincode/ToRon.h".to_string(),
        ]);

        for field in &self.fields {
            match (field.field_type.base_type.pkg_name(), field.field_type.base_type.msg_name()) {
                (Some(pkg_name), msg_name) => {
                    includes.insert(format!("{}/{}.h", pkg_name, msg_name.to_case(Case::Pascal)));
                },
                (None, msg_name) => match msg_name {
                    "string" => {
                        includes.insert("string".to_string());
                    },
                    "time" => {
                        includes.insert("ohmw_framework/message/Time.h".to_string());
                    },
                    "duration" => {
                        includes.insert("ohmw_framework/message/Duration.h".to_string());
                    },
                    _ => {}
                },
            }
        }

        for include in includes {
            writeln!(f, "#include <{}>", include)?;
        }

        Ok(())
    }

    fn gen_cxx_class<W: std::fmt::Write>(&self, msg_name: &str, mut f: W) -> std::fmt::Result {
        writeln!(f, "class {} {{", msg_name.to_case(Case::Pascal))?;
        writeln!(f, "public:")?;
        
        for field in &self.fields {
            let base_type = &field.field_type.base_type;
            if field.field_type.is_array {
                if let Some(size) = field.field_type.array_size {
                    writeln!(f, "    {} {}[{}];", base_type.to_cxx_type(), field.field_name_to_cxx(), size)?;
                } else {
                    writeln!(f, "    std::vector<{}> {};", base_type.to_cxx_type(), field.field_name_to_cxx())?;
                }
            } else {
                writeln!(f, "    {} {};", base_type.to_cxx_type(), field.field_name_to_cxx())?;
            }
        }
        if !self.fields.is_empty() {
            writeln!(f)?;
        }

        writeln!(f, "    template<typename E>")?;
        writeln!(f, "    void EncodeUnchecked(E& encoder, std::vector<uint8_t>& buffer) const {{")?;
        for field in &self.fields {
            field.gen_cxx_encode_unchecked_stmt(&mut f)?;
        }
        writeln!(f, "    }}")?;
        writeln!(f)?;

        writeln!(f, "    template<typename E>")?;
        writeln!(f, "    bool Encode(E& encoder, std::vector<uint8_t>& buffer) const {{")?;
        for field in &self.fields {
            field.gen_cxx_encode_stmt(&mut f)?;
        }
        writeln!(f, "        return true;")?;
        writeln!(f, "    }}")?;
        writeln!(f)?;

        writeln!(f, "    template<typename D>")?;
        writeln!(f, "    static void DecodeUnchecked(D& decoder, const std::vector<uint8_t>& buffer, {}& msg) {{",
            msg_name.to_case(Case::Pascal))?;
        for field in &self.fields {
            field.gen_cxx_decode_unchecked_stmt(&mut f)?;
        }
        writeln!(f, "    }}")?;
        writeln!(f)?;

        writeln!(f, "    template<typename D>")?;
        writeln!(f, "    static bool Decode(D& decoder, const std::vector<uint8_t>& buffer, {}& msg) {{",
            msg_name.to_case(Case::Pascal))?;
        for field in &self.fields {
            field.gen_cxx_decode_stmt(&mut f)?;
        }
        writeln!(f, "        return true;")?;
        writeln!(f, "    }}")?;
        writeln!(f)?;

        writeln!(f, "    std::ostream& ToRon(std::ostream& os) const {{")?;
        writeln!(f, "        os << \"(\";")?;
        for (i, field) in self.fields.iter().enumerate() {
            field.gen_cxx_to_ron_stmt(i == 0, &mut f)?;
        }
        writeln!(f, "        os << \")\";")?;
        writeln!(f, "        return os;")?;
        writeln!(f, "    }}")?;

        writeln!(f, "}};")?;
        Ok(())
    }
}

impl Field {
    fn gen_cxx_encode_unchecked_stmt<W: std::fmt::Write>(&self, f: &mut W) -> std::fmt::Result {
        let base_type = &self.field_type.base_type;
        if self.field_type.is_array {
            if let Some(size) = self.field_type.array_size {
                if matches!(base_type.msg_name(), "byte" | "char" | "uint8") {
                    writeln!(f, "        encoder.PutUnchecked(buffer, {}, size);", self.field_name_to_cxx())?;
                } else {
                    writeln!(f, "        for (size_t i = 0; i < {}; ++i) {{", size)?;
                    if base_type.is_builtin() && !matches!(base_type.msg_name(), "time" | "duration") {
                        writeln!(f, "            encoder.PutUnchecked(buffer, {}[i]);", self.field_name_to_cxx())?;
                    } else {
                        writeln!(f, "            {}[i].EncodeUnchecked(encoder, buffer);", self.field_name_to_cxx())?;
                    }
                    writeln!(f, "        }}")?;
                }
            } else {
                writeln!(f, "        encoder.PutUnchecked(buffer, static_cast<uint32_t>({}.size()));",
                    self.field_name_to_cxx())?;
                if matches!(base_type.msg_name(), "byte" | "char" | "uint8") {
                    writeln!(f, "        encoder.PutUnchecked(buffer, {}.data(), {}.size());",
                        self.field_name_to_cxx(), self.field_name_to_cxx())?;
                } else {
                    writeln!(f, "        for (const auto& item : {}) {{", self.field_name_to_cxx())?;
                    if base_type.is_builtin() && !matches!(base_type.msg_name(), "time" | "duration") {
                        writeln!(f, "            encoder.PutUnchecked(buffer, item);")?;
                    } else {
                        writeln!(f, "            item.EncodeUnchecked(encoder, buffer);")?;
                    }
                    writeln!(f, "        }}")?;
                }
            }
            
        } else {
            if base_type.is_builtin() && !matches!(base_type.msg_name(), "time" | "duration") {
                writeln!(f, "        encoder.PutUnchecked(buffer, {});", self.field_name_to_cxx())?;
            } else {
                writeln!(f, "        {}.EncodeUnchecked(encoder, buffer);", self.field_name_to_cxx())?;
            }
        }
        Ok(())
    }

    fn gen_cxx_encode_stmt<W: std::fmt::Write>(&self, f: &mut W) -> std::fmt::Result {
        let base_type = &self.field_type.base_type;
        if self.field_type.is_array {
            if let Some(size) = self.field_type.array_size {
                if matches!(base_type.msg_name(), "byte" | "char" | "uint8") {
                    writeln!(f, "        if (!encoder.Put(buffer, {}, size)) {{", self.field_name_to_cxx())?;
                    writeln!(f, "            return false;")?;
                    writeln!(f, "        }}")?;
                } else {
                    writeln!(f, "        for (size_t i = 0; i < {}; ++i) {{", size)?;
                    if base_type.is_builtin() && !matches!(base_type.msg_name(), "time" | "duration") {
                        writeln!(f, "            if (!encoder.Put(buffer, {}[i])) {{", self.field_name_to_cxx())?;
                    } else {
                        writeln!(f, "            if (!{}[i].Encode(encoder, buffer)) {{", self.field_name_to_cxx())?;
                    }
                    writeln!(f, "                return false;")?;
                    writeln!(f, "            }}")?;
                    writeln!(f, "        }}")?;
                }
            } else {
                writeln!(f, "        if (!encoder.Put(buffer, static_cast<uint32_t>({}.size()))) {{",
                    self.field_name_to_cxx())?;
                writeln!(f, "            return false;")?;
                writeln!(f, "        }}")?;
                if matches!(base_type.msg_name(), "byte" | "char" | "uint8") {
                    writeln!(f, "        if (!encoder.Put(buffer, {}.data(), {}.size())) {{",
                        self.field_name_to_cxx(), self.field_name_to_cxx())?;
                    writeln!(f, "            return false;")?;
                    writeln!(f, "        }}")?;
                } else {
                    writeln!(f, "        for (const auto& item : {}) {{", self.field_name_to_cxx())?;
                    if base_type.is_builtin() && !matches!(base_type.msg_name(), "time" | "duration") {
                        writeln!(f, "            if (!encoder.Put(buffer, item)) {{")?;
                    } else {
                        writeln!(f, "            if (!item.Encode(encoder, buffer)) {{")?;
                    }
                    writeln!(f, "                return false;")?;
                    writeln!(f, "            }}")?;
                    writeln!(f, "        }}")?;
                }
            }
        } else {
            if base_type.is_builtin() && !matches!(base_type.msg_name(), "time" | "duration") {
                writeln!(f, "        if (!encoder.Put(buffer, {})) {{", self.field_name_to_cxx())?;
            } else {
                writeln!(f, "        if (!{}.Encode(encoder, buffer)) {{", self.field_name_to_cxx())?;
            }
            writeln!(f, "            return false;")?;
            writeln!(f, "        }}")?;
        }
        Ok(())
    }

    fn gen_cxx_decode_unchecked_stmt<W: std::fmt::Write>(&self, f: &mut W) -> std::fmt::Result {
        let base_type = &self.field_type.base_type;
        if base_type.is_builtin() && !matches!(base_type.msg_name(), "time" | "duration") {
            if self.field_type.is_array {
                if matches!(base_type.msg_name(), "byte" | "char" | "uint8") {
                    if let Some(size) = self.field_type.array_size {
                        writeln!(f, "        decoder.GetUnchecked(buffer, msg.{}, {size});", self.field_name_to_cxx())?;
                    } else {
                        writeln!(f, "        uint32_t {}_size;", self.field_name_to_cxx())?;
                        writeln!(f, "        decoder.GetUnchecked(buffer, {}_size);", self.field_name_to_cxx())?;
                        writeln!(f, "        msg.{}.resize({}_size);", self.field_name_to_cxx(), self.field_name_to_cxx())?;
                        writeln!(f, "        decoder.GetUnchecked(buffer, msg.{}.data(), {}_size);",
                            self.field_name_to_cxx(), self.field_name_to_cxx())?;
                    }
                } else {
                    if let Some(size) = self.field_type.array_size {
                        writeln!(f, "        for (size_t i = 0; i < {}; ++i) {{", size)?;
                    } else {
                        writeln!(f, "        uint32_t {}_size;", self.field_name_to_cxx())?;
                        writeln!(f, "        decoder.GetUnchecked(buffer, {}_size);", self.field_name_to_cxx())?;
                        writeln!(f, "        msg.{}.resize({}_size);", self.field_name_to_cxx(), self.field_name_to_cxx())?;
                        writeln!(f, "        for (size_t i = 0; i < {}_size; ++i) {{", self.field_name_to_cxx())?;
                    }
                    writeln!(f, "            decoder.GetUnchecked(buffer, msg.{}[i]);", self.field_name_to_cxx())?;
                    writeln!(f, "        }}")?;
                }
            } else {
                writeln!(f, "        decoder.GetUnchecked(buffer, msg.{});", self.field_name_to_cxx())?;
            }
        } else {
            let type_prefix = match (base_type.pkg_name(), base_type.msg_name()) {
                (Some(pkg_name), msg_name) => format!("{}::{}", pkg_name, msg_name.to_case(Case::Pascal)),
                (None, msg_name) => match msg_name {
                    "time" => "OHOS::OHMW::Time".to_string(),
                    "duration" => "OHOS::OHMW::Duration".to_string(),
                    _ => return Ok(()),
                },
            };
            
            if self.field_type.is_array {
                if let Some(size) = self.field_type.array_size {
                    writeln!(f, "        for (size_t i = 0; i < {}; ++i) {{", size)?;
                } else {
                    writeln!(f, "        uint32_t {}_size;", self.field_name_to_cxx())?;
                    writeln!(f, "        decoder.GetUnchecked(buffer, {}_size);", self.field_name_to_cxx())?;
                    writeln!(f, "        msg.{}.resize({}_size);", self.field_name_to_cxx(), self.field_name_to_cxx())?;
                    writeln!(f, "        for (size_t i = 0; i < {}_size; ++i) {{", self.field_name_to_cxx())?;
                }
                writeln!(f, "            {}::DecodeUnchecked(decoder, buffer, msg.{}[i]);",
                    type_prefix, self.field_name_to_cxx())?;
                writeln!(f, "        }}")?;
            } else {
                writeln!(f, "        {}::DecodeUnchecked(decoder, buffer, msg.{});",
                    type_prefix, self.field_name_to_cxx())?;
            }
        }
        Ok(())
    }

    fn gen_cxx_decode_stmt<W: std::fmt::Write>(&self, f: &mut W) -> std::fmt::Result {
        let base_type = &self.field_type.base_type;
        if base_type.is_builtin() && !matches!(base_type.msg_name(), "time" | "duration") {
            if self.field_type.is_array {
                if matches!(base_type.msg_name(), "byte" | "char" | "uint8") {
                    if let Some(size) = self.field_type.array_size {
                        writeln!(f, "        if (!decoder.Get(buffer, msg.{}, {size})) {{", self.field_name_to_cxx())?;
                    } else {
                        writeln!(f, "        uint32_t {}_size;", self.field_name_to_cxx())?;
                        writeln!(f, "        if (!decoder.Get(buffer, {}_size)) {{", self.field_name_to_cxx())?;
                        writeln!(f, "            return false;")?;
                        writeln!(f, "        }}")?;
                        writeln!(f, "        msg.{}.resize({}_size);", self.field_name_to_cxx(), self.field_name_to_cxx())?;
                        writeln!(f, "        if (!decoder.Get(buffer, msg.{}.data(), {}_size)) {{",
                            self.field_name_to_cxx(), self.field_name_to_cxx())?;
                    }
                    writeln!(f, "            return false;")?;
                    writeln!(f, "        }}")?;
                } else {
                    if let Some(size) = self.field_type.array_size {
                        writeln!(f, "        for (size_t i = 0; i < {}; ++i) {{", size)?;
                    } else {
                        writeln!(f, "        uint32_t {}_size;", self.field_name_to_cxx())?;
                        writeln!(f, "        if (!decoder.Get(buffer, {}_size)) {{", self.field_name_to_cxx())?;
                        writeln!(f, "            return false;")?;
                        writeln!(f, "        }}")?;
                        writeln!(f, "        msg.{}.resize({}_size);", self.field_name_to_cxx(), self.field_name_to_cxx())?;
                        writeln!(f, "        for (size_t i = 0; i < {}_size; ++i) {{", self.field_name_to_cxx())?;
                    }
                    writeln!(f, "            if (!decoder.Get(buffer, msg.{}[i])) {{", self.field_name_to_cxx())?;
                    writeln!(f, "                return false;")?;
                    writeln!(f, "            }}")?;
                    writeln!(f, "        }}")?;
                }
            } else {
                writeln!(f, "        if (!decoder.Get(buffer, msg.{})) {{", self.field_name_to_cxx())?;
                writeln!(f, "            return false;")?;
                writeln!(f, "        }}")?;
            }
        } else {
            let type_prefix = match (base_type.pkg_name(), base_type.msg_name()) {
                (Some(pkg_name), msg_name) => format!("{}::{}", pkg_name, msg_name.to_case(Case::Pascal)),
                (None, msg_name) => match msg_name {
                    "time" => "OHOS::OHMW::Time".to_string(),
                    "duration" => "OHOS::OHMW::Duration".to_string(),
                    _ => return Ok(()),
                },
            };

            if self.field_type.is_array {
                if let Some(size) = self.field_type.array_size {
                    writeln!(f, "        for (size_t i = 0; i < {}; ++i) {{", size)?;
                    writeln!(f, "            if (!{}::Decode(decoder, buffer, msg.{}[i])) {{",
                        type_prefix, self.field_name_to_cxx())?;
                } else {
                    writeln!(f, "        uint32_t {}_size;", self.field_name_to_cxx())?;
                    writeln!(f, "        if (!decoder.Get(buffer, {}_size)) {{", self.field_name_to_cxx())?;
                    writeln!(f, "            return false;")?;
                    writeln!(f, "        }}")?;
                    writeln!(f, "        msg.{}.resize({}_size);", self.field_name_to_cxx(), self.field_name_to_cxx())?;
                    writeln!(f, "        for (size_t i = 0; i < {}_size; ++i) {{", self.field_name_to_cxx())?;
                    writeln!(f, "            if (!{}::Decode(decoder, buffer, msg.{}[i])) {{",
                        type_prefix, self.field_name_to_cxx())?;
                }
                writeln!(f, "                return false;")?;
                writeln!(f, "            }}")?;
                writeln!(f, "        }}")?;
            } else {
                writeln!(f, "        if (!{}::Decode(decoder, buffer, msg.{})) {{",
                    type_prefix, self.field_name_to_cxx())?;
                writeln!(f, "            return false;")?;
                writeln!(f, "        }}")?;
            }
        }
        Ok(())
    }

    fn gen_cxx_to_ron_stmt<W: std::fmt::Write>(&self, first: bool, f: &mut W) -> std::fmt::Result {
        if !first {
            writeln!(f, "        os << \",\";")?;
        }

        let base_type = &self.field_type.base_type;
        let value = if self.field_type.is_array {
            format!("{}[i]", self.field_name_to_cxx())
        } else {
            self.field_name_to_cxx()
        };
        let eval = if base_type.is_builtin() && !matches!(base_type.msg_name(), "time" | "duration") {
            format!("OHOS::OHMW::ToRon(os, {})", value)
        } else {
            format!("{}.ToRon(os)", value)
        };
        
        if self.field_type.is_array {
            let array_bounder_left = if self.field_type.array_size.is_some() {
                "("
            } else {
                "["
            };
            let array_bounder_right = if self.field_type.array_size.is_some() {
                ")"
            } else {
                "]"
            };
            writeln!(f, "        os << \"{}:{}\";", self.field_name.to_case(Case::Snake), array_bounder_left)?;
            if let Some(size) = self.field_type.array_size {
                writeln!(f, "        for (size_t i = 0; i < {}; ++i) {{", size)?;
            } else {
                writeln!(f, "        for (size_t i = 0; i < {}.size(); ++i) {{", self.field_name_to_cxx())?;
            }
            writeln!(f, "            if (i > 0) {{")?;
            writeln!(f, "                os << \",\";")?;
            writeln!(f, "            }}")?;
            writeln!(f, "            {};", eval)?;
            writeln!(f, "        }}")?;
            writeln!(f, "        os << \"{}\";", array_bounder_right)?;
        } else {
            writeln!(f, "        os << \"{}:\";", self.field_name.to_case(Case::Snake))?;
            writeln!(f, "        {};", eval)?;
        }
        Ok(())
    }

    pub fn field_name_to_cxx(&self) -> String {
        let s = self.field_name.to_case(Case::Snake);
        match s.as_str() {
            "alignas" | "alignof" | "and" | "and_eq" | "asm" | "auto" | "bitand" | "bitor" | "bool" | "break" |
            "case" | "catch" | "char" | "char8_t" | "char16_t" | "char32_t" | "class" | "compl" | "concept" |
            "const" | "consteval" | "constexpr" | "const_cast" | "continue" | "co_await" | "co_return" |
            "co_yield" | "decltype" | "default" | "delete" | "do" | "double" | "dynamic_cast" | "else" |
            "enum" | "explicit" | "export" | "extern" | "false" | "float" | "for" | "friend" | "goto" |
            "if" | "inline" | "int" | "long" | "mutable" | "namespace" | "new" | "noexcept" | "not" |
            "not_eq" | "nullptr" | "operator" | "or" | "or_eq" | "private" | "protected" | "public" |
            "register" | "reinterpret_cast" | "requires" | "return" | "short" | "signed" | "sizeof" |
            "static" | "static_assert" | "static_cast" | "struct" | "switch" | "template" | "this" |
            "thread_local" | "throw" | "true" | "try" | "typedef" | "typeid" | "typename" | "union" |
            "unsigned" | "using" | "virtual" | "void" | "volatile" | "wchar_t" | "while" | "xor" | "xor_eq" => {
                format!("_{}", s)
            },
            _ => s
        }
    }
}

impl BaseType {
    pub fn to_cxx_type(&self) -> String {
        match (self.pkg_name(), self.msg_name()) {
            (Some(pkg_name), msg_name) => format!("{}::{}",
                pkg_name, msg_name.to_case(Case::Pascal)),
            (None, msg_name) => match msg_name {
                "bool" => "bool".to_string(),
                "byte" => "uint8_t".to_string(),
                "char" => "char".to_string(),
                "int8" => "int8_t".to_string(),
                "uint8" => "uint8_t".to_string(),
                "int16" => "int16_t".to_string(),
                "uint16" => "uint16_t".to_string(),
                "int32" => "int32_t".to_string(),
                "uint32" => "uint32_t".to_string(),
                "int64" => "int64_t".to_string(),
                "uint64" => "uint64_t".to_string(),
                "float32" => "float".to_string(),
                "float64" => "double".to_string(),
                "string" => "std::string".to_string(),
                "time" => "OHOS::OHMW::Time".to_string(),
                "duration" => "OHOS::OHMW::Duration".to_string(),
                other => format!("not_found::{}", other.to_case(Case::Pascal)),
            },
        }
    }
}
